package com.dm.cloud.images.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.dm.cloud.images.mode.CommonProp;
import com.dm.cloud.images.mode.fileupload.*;
import com.dm.cloud.images.util.LocalUtil;
import com.dm.cloud.images.util.NfsFileUtil;
import com.dm.cloud.tkmybatis.service.BaseMapper;
import com.dm.cloud.images.mapper.ImagesShardMapper;
import com.dm.cloud.images.service.ImagesShardService;
import com.dm.cloud.images.common.po.ImagesShard;
import com.dm.cloud.core.exception.CustomException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
   * @author hcx
   * @type ImagesShardServiceImpl.java
   * @desc ImagesShard serviceImpl
   */
@Service

@Transactional

public class ImagesShardServiceImpl implements ImagesShardService {



    @Autowired
    private ImagesShardMapper imagesShardMapper;

    @Autowired
    private LocalUtil localUtil;

    @Override
    public BaseMapper getMapper() {
        return imagesShardMapper;
    }
    public void processBeforeOperation(ImagesShard entity, BaseOperationEnum operationEnum) throws CustomException {
        if (operationEnum.equals(BaseOperationEnum.INSERT)) {
            entity.setRootPath(IdUtil.fastSimpleUUID()); //设置共享配置项根目录
        }
    }
    public void processAfterOperation(ImagesShard entity, BaseOperationEnum operationEnum) throws CustomException {
        if(operationEnum.equals(BaseOperationEnum.INSERT)){
            //构建目录
            String rootPath = entity.getRootPath();
            File sharDir=NfsFileUtil.mkChildDir(rootPath);
        }else if(operationEnum.equals(BaseOperationEnum.DELETE) || operationEnum.equals(BaseOperationEnum.DELETE_BATCH)){
            try {
                File sharDir=NfsFileUtil.getChildDir(entity.getRootPath());
                FileUtil.del(sharDir);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    @Override
    public FileUploadShardStartResultMode shardStart(FileUploadShardStartMode fileUploadShardStartMode) {
        if(StrUtil.isEmpty(fileUploadShardStartMode.getMd5hash())){
            throw new CustomException("文件md5码不能为空!");
        }
        if(fileUploadShardStartMode.getShardSize()==null || fileUploadShardStartMode.getShardSize()<=0){
            throw new CustomException("分片数据量不能为空!");
        }
        String md5Hash = fileUploadShardStartMode.getMd5hash();
        FileUploadShardStartResultMode re=new FileUploadShardStartResultMode();
        re.setFileKey(md5Hash);

        File hashDir = localUtil.getShardWork(re.getFileKey());
        List<Integer> notUps=new ArrayList<>();
        for (Integer i = 0; i < fileUploadShardStartMode.shardSize; i++) {
            notUps.add(i);
        }
        if(!hashDir.exists()){
            hashDir.mkdirs();
        }else{
            hashDir.list(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    if(name.startsWith(CommonProp.SHARD_FILE_PREFIX)){
                        try {
                            String indexStr = name.replace(CommonProp.SHARD_FILE_PREFIX, "");
                            Integer index=Integer.valueOf(indexStr);
                            if(notUps.contains(index)){
                                notUps.remove(notUps.indexOf(index));
                            }
                            return true;
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                    return false;
                }
            });
        }
        re.setNotUp(notUps);
        return re;
    }

    @Override
    public Boolean shardUpload(MultipartFile file, String fileKey, Integer shardNum) {
        if(StrUtil.isEmpty(fileKey)){
            throw new CustomException("fileKey不能为空！");
        }
        if(shardNum==null || shardNum<0){
            throw new CustomException("分片编号不能为空!");
        }
        File hashDir = localUtil.getShardWork(fileKey);
        if(!hashDir.exists() || !hashDir.isDirectory()){
            throw new CustomException("分片存储不存在");
        }
        File shardFile= FileUtil.newFile(hashDir.getAbsolutePath()+File.separator+CommonProp.SHARD_FILE_PREFIX+shardNum);
        if(!shardFile.exists()){
            try {
                shardFile.createNewFile();
            }catch (Exception e){
                throw new CustomException("创建分片文件失败!");
            }
        }
        try {
            file.transferTo(shardFile);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CustomException("保存文件失败!");
        }
        return true;
    }

    @Override
    public String shardDir(FileDirMkdir fileMkdir) {
        if(StrUtil.isEmpty(fileMkdir.getFilePath())){
            throw new CustomException("存储文件名不能为空！");
        }
        if(StrUtil.isEmpty(fileMkdir.getShardId())){
            throw new CustomException("共享存储标识不存在！");
        }
        ImagesShard shard = getById(fileMkdir.getShardId());
        //保存在共享目录中的根路径
        String fileSavePath=fileMkdir.getFilePath();
        //nfs路径
        File f=NfsFileUtil.mkChildDir(shard.getRootPath());
        File saveFile=new File(f.getAbsolutePath()+File.separator+fileSavePath);
        if(!saveFile.exists() || !saveFile.isDirectory()){
            saveFile.mkdirs();
        }
        return fileSavePath;
    }
    @Override
    public String shardMerge(FileUploadMerge fileUploadMerge) {
        if(StrUtil.isEmpty(fileUploadMerge.getFileKey())){
            throw new CustomException("fileKey不能为空！");
        }
        if(StrUtil.isEmpty(fileUploadMerge.getFilePath())){
            throw new CustomException("存储文件名不能为空！");
        }
        //本地分片目录
        File hashDir = localUtil.getShardWork(fileUploadMerge.getFileKey());
        if(!hashDir.exists() || !hashDir.isDirectory()){
            throw new CustomException("分片存储不存在");
        }
        if(StrUtil.isEmpty(fileUploadMerge.getShardId())){
            throw new CustomException("共享存储标识不存在！");
        }
        ImagesShard shard = getById(fileUploadMerge.getShardId());
        //保存在共享目录中的根路径
        String fileSavePath=fileUploadMerge.getFilePath();
        //nfs路径
        File f=NfsFileUtil.mkChildDir(shard.getRootPath());
        File saveFile=new File(f.getAbsolutePath()+File.separator+fileSavePath);
        if(!saveFile.getParentFile().exists()){
            saveFile.getParentFile().mkdirs();
        }
        //创建文件,存储的nfs文件
        saveFile=FileUtil.touch(saveFile);
        FileOutputStream out = null;
        try {
            out=new FileOutputStream(saveFile);
            boolean next=true;
            int shardIndex=0;
            while (next){
                String shardName=CommonProp.SHARD_FILE_PREFIX+shardIndex;
                File shardFile=new File(hashDir+File.separator+shardName);
                if(!shardFile.exists()){
                    next=false;
                    continue;
                }
                FileInputStream in = null;
                try {
                    in=new FileInputStream(shardFile);
                    IoUtil.copy(in.getChannel(),out.getChannel());
                }catch (Exception e){
                    e.printStackTrace();
                    throw new CustomException("分片文件合并失败!");
                }finally {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                shardIndex++;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw new CustomException("共享文件保存失败！");
        }finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return fileSavePath;
    }

    @Override
    public List<FileMode> shardFiles(String shardId) {
        if(StrUtil.isEmpty(shardId)){
            throw new CustomException("分享目录id不能为空!");
        }
        File rootDir = shardRootDir(shardId);
        List<FileMode> files=new ArrayList<>();
        List<File> nextDirs=new ArrayList<>();
        nextDirs.add(rootDir);
        while (nextDirs.size()>0){
            File firstDir = nextDirs.remove(0);
            for (File child : firstDir.listFiles()) {
                String nfsRelativePath = NfsFileUtil.relativePath(child);//相对于nfs目录
                //再去掉root目录
                nfsRelativePath=nfsRelativePath.substring(nfsRelativePath.lastIndexOf(rootDir.getName())+rootDir.getName().length());
                nfsRelativePath=nfsRelativePath.replace("\\","/");
                FileMode fileMode=FileMode
                        .builder()
                        .fileName(child.getName())
                        .filePath(nfsRelativePath)
                        .parentDir(nfsRelativePath.replace("/"+child.getName(),""))
                        .fileSize(child.length())
                        .isFile(child.isFile())
                        .build();
                files.add(fileMode);
                if(child.isDirectory()){
                    nextDirs.add(child);
                }
            }

        }
        return files;
    }

    @Override
    public String getEditContent(String shardId, String filePath, String charCode) {
        if(StrUtil.isEmpty(filePath)){
            throw new CustomException("要打开的文件不能为空!");
        }
        filePath=filePath.replace("\\","/");
        String ext = FileNameUtil.extName(filePath);
        if(StrUtil.isEmpty(ext)){
            throw new CustomException("此文件类型不支持在线编辑,请上传替换!");
        }
        if(!NfsFileUtil.hasEditTypes(ext)){
            throw new CustomException("此文件类型不支持在线编辑,请上传替换!");
        }
        File rootDir = shardRootDir(shardId);
        File contentFile=new File(rootDir.getAbsolutePath()+File.separator+filePath);
        if(!contentFile.exists() || !contentFile.isFile()){
            throw new CustomException("文件不存在!");
        }
        //验证长度
        NfsFileUtil.hasLengthEdit(contentFile.length());
        String content=FileUtil.readString(contentFile, Charset.forName(charCode));
        return content;
    }

    @Override
    public Boolean saveEditContent(SaveEditContentMode saveEditContentMode) {
        String ext = FileNameUtil.extName(saveEditContentMode.getFilePath());
        if(StrUtil.isEmpty(ext)){
            throw new CustomException("此文件类型不支持在线编辑,请上传替换!");
        }
        if(!NfsFileUtil.hasEditTypes(ext)){
            throw new CustomException("此文件类型不支持在线编辑,请上传替换!");
        }
        if(StrUtil.isNotEmpty(saveEditContentMode.getFilePath())){
            saveEditContentMode.setFilePath(saveEditContentMode.getFilePath().replace("\\","/"));
        }
        File rootDir = shardRootDir(saveEditContentMode.getShardId());
        File contentFile=new File(rootDir.getAbsolutePath()+File.separator+saveEditContentMode.getFilePath());
        if(!contentFile.exists() ||  !contentFile.isFile()){
            throw new CustomException("文件不存在!");
        }
        try {
            FileUtil.writeString(saveEditContentMode.getContent(),contentFile,Charset.forName(saveEditContentMode.getCharCode()));
        }catch (Exception e){
            if(e.getMessage().contains("拒绝访问")){
                throw new CustomException("文件拒绝访问，正在被使用！");
            }
        }

        return true;
    }

    @Override
    public Boolean deleteFile(DeleteFileModel deleteFileModel) {
        if(deleteFileModel.getFilePaths()==null || deleteFileModel.getFilePaths().isEmpty()){
            throw new CustomException("请选择要删除的文件");
        }
        File shardRootDir = shardRootDir(deleteFileModel.getShardId());
        String rootAbs=shardRootDir.getAbsolutePath();
        for (String filePath : deleteFileModel.getFilePaths()) {
            if(StrUtil.isEmpty(filePath)){
                continue;
            }
            //不能删除根目录
            if(StrUtil.equalsAny(filePath.trim(),"/","\\")){
                continue;
            }
            filePath=filePath.replace("\\","/");
            try {
                File delFile=new File(rootAbs+File.separator+filePath);
                if(delFile.exists()){
                    FileUtil.del(delFile);
                }
            }catch (Exception e){
                if(e.getMessage().contains("AccessDeniedException:")){
                    throw new CustomException("没有权限删除，请确认是否正在使用！");
                }
            }
        }
        return true;
    }

    @Override
    public void export(String shardId, String filePath, HttpServletResponse response) {
        File shardRootDir = shardRootDir(shardId);
        File exportFile=null;
        if(StrUtil.isNotEmpty(filePath)){
            filePath=filePath.replace("\\","/");
        }
        if(StrUtil.isEmpty(filePath)
                || filePath.replace("\\","/").toLowerCase(Locale.ROOT).trim().equals("/")
        ){
            exportFile=shardRootDir;
        }else{
            exportFile=new File(shardRootDir.getAbsolutePath()+File.separator+filePath);
        }
        if(!exportFile.exists()){
            throw new CustomException("要导出的文件不存在!");
        }
        if(exportFile.isDirectory()){
            String zipPath=localUtil.curDayDir().getAbsolutePath()+File.separator+exportFile.getName()+".zip";
            File zipFile = FileUtil.touch(zipPath);
            ZipUtil.zip(zipFile,StandardCharsets.UTF_8,false,exportFile);
            exportFile=zipFile;
        }
        response.setHeader("Content-Disposition", "attachment;filename="+ URLEncodeUtil.encode(exportFile.getName()));
        Optional<MediaType> mediaType = MediaTypeFactory.getMediaType(exportFile.getName());
        mediaType.ifPresent(type -> response.setContentType(type.toString()));
        FileInputStream in=null;
        ServletOutputStream out = null;
        try {
            in=new FileInputStream(exportFile);
            out= response.getOutputStream();
            IoUtil.copy(in,out);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                in.close();
            }catch (Exception e){}
            try {
                out.flush();
                out.close();
            }catch (Exception e){}
        }
    }

    @Override
    public String ifShardReturnRootPath(String shardPath, String nodeDockerShardDir) {
        //shard_{sharid}:/dsad/dsad/sa/ds/a
        if(shardPath!=null && shardPath.matches(CommonProp.SHARD_VOLUME_PREFIX+".*")){
            shardPath=shardPath.replace("\\","/");
            String[] shardArr = shardPath.split(":");
            String shardId=shardArr[0].substring((CommonProp.SHARD_VOLUME_PREFIX).length());
            ImagesShard shard = getById(shardId);
            if(shard==null){
                throw new CustomException("共享目录不存在！");
            }
            if(shardArr.length>1){
                String sharChild=shardArr[1].startsWith("/")?shardArr[1]:"/"+shardArr[1];
                return nodeDockerShardDir+"/"+shard.getRootPath()+sharChild;
            }else{
                return nodeDockerShardDir+"/"+shard.getRootPath();
            }
        }
        return null;
    }



    /**
     * 获取分片根目录
     * @return
     */
    public File shardRootDir(String shardId){
        ImagesShard shards = getById(shardId);
        if(shards==null){
            throw new CustomException("分享目录不存在!");
        }
        String shardRootPath = shards.getRootPath();
        File rootDir = NfsFileUtil.getChildDir(shardRootPath);
        return rootDir;
    }

}
